from hashlib import sha256

import time
import json
import ecdsa

SECRET_KEY = 'SZTU'
CONNECTED_NODE_ADDRESS = "http://127.0.0.1:8000"


def create_accounts():
    accounts = []
    for i in range(10):
        seed = str(time.time())
        time.sleep(0.001)
        account = sha256(seed.encode()).hexdigest()
        accounts.append(account)
    return accounts


# Generate new Keys
privateKey = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1)
publicKey = privateKey.get_verifying_key()

message = "Testing message for validation."

# Generate Signature
signature = privateKey.sign(message.encode())


# To verify if the signature is valid
# print(Ecdsa.verify(message, signature, publicKey))

class Block:
    def __init__(self, index, transactions, timestamp, previous_hash, hash=0, nonce=0):
        self.index = index
        self.transactions = transactions
        self.timestamp = timestamp
        self.previous_hash = previous_hash
        self.nonce = nonce
        self.hash = hash

    def _calculate_hash(self):
        block_string = json.dumps(self.__dict__, sort_keys=True, indent=4)
        return sha256(block_string.encode()).hexdigest()


class Blockchain:
    # difficulty of our PoW algorithm
    difficulty = 2

    def __init__(self):
        self.unconfirmed_transactions = []
        self.chain = []
        #self.create_genesis_block()  # 加上这一行

    def create_genesis_block(self):
        '''
        A function to generate genesis block and appends it to
        the chain. The block has index 0, previous_hash as 0, and
        a valid hash.
        '''
        genesis_block = Block(0, [], 0, "0")
        genesis_block.hash = genesis_block._calculate_hash()
        self.chain.append(genesis_block)
        return genesis_block

    @property
    # part 1
    def last_block(self):
        if len(self.chain) > 0:
            return self.chain[-1]
        return None

    # part 2
    def get_height(self):
        return len(self.chain)

    # part 3
    def get_block(self, index):
        if index < len(self.chain):
            return self.chain[index].__dict__
        return None

    # part 4
    def add_block(self, block, proof):
        previous_hash = self.last_block.hash
        if previous_hash != block.previous_hash:
            return False
        if not self.is_valid_proof(block, proof):
            return False
        block.hash = proof
        self.chain.append(block)
        return True

    # part 5
    @staticmethod
    def proof_of_work(block):
        block.nonce = 0
        computed_hash = block._calculate_hash()
        while not computed_hash.startswith('0' * Blockchain.difficulty):
            block.nonce += 1
            computed_hash = block._calculate_hash()
        return computed_hash

    # part 6
    def add_new_transaction(self, transaction):
        self.unconfirmed_transactions.append(transaction)

    # part 7
    def send_transaction(self, sender, receiver, data):
        transaction = {
            'sender': sender,
            'receiver': receiver,
            'data': data,
            'timestamp': time.time()
        }
        self.add_new_transaction(transaction)
        return True

    # part 8
    def _send_transaction(self, sender, receiver, data, privateKey):
        transaction = {
            'sender': sender,
            'receiver': receiver,
            'data': data,
            'timestamp': time.time()
        }
        message = json.dumps(transaction, sort_keys=True)
        signature = privateKey.sign(message.encode()).hex()
        transaction['signature'] = signature
        self.add_new_transaction(transaction)
        return True

    # part 9
    @classmethod
    def is_valid_proof(cls, block, block_hash):
        return (block_hash.startswith('0' * cls.difficulty) and
                block_hash == block._calculate_hash())


    @classmethod
    def check_chain_validity(cls, chain):
        result = True
        previous_hash = "0"

        for block in chain:
            block_hash = block.hash
            # remove the hash field to recompute the hash again
            # using `_calculate_hash` method.
            delattr(block, "hash")

            if not cls.is_valid_proof(block, block_hash) or \
                    previous_hash != block.previous_hash:
                result = False
                break

            block.hash, previous_hash = block_hash, block_hash

        return result

    # 通过相邻区块previous_hash的一致性来检测区块链的完整性
    # part 10
    def check_block_validity(self, chain):
        for i in range(1, len(chain)):
            prev_block = chain[i - 1]
            curr_block = chain[i]
            if curr_block.previous_hash != prev_block.hash:
                return False
            if not self.is_valid_proof(curr_block, curr_block.hash):
                return False
        return True

    def mine(self):
        """
        This function serves as an interface to add the pending
        transactions to the blockchain by adding them to the block
        and figuring out Proof Of Work.
        """
        if not self.unconfirmed_transactions:
            return False

        last_block = self.last_block

        new_block = Block(index=last_block.index + 1,
                          transactions=self.unconfirmed_transactions,
                          timestamp=time.time(),
                          previous_hash=last_block.hash)

        proof = self.proof_of_work(new_block)
        self.add_block(new_block, proof)

        self.unconfirmed_transactions = []

        return True

    def _mine(self, publickey, signature, message):
        if not self.unconfirmed_transactions:
            return False

        if publickey.verify(signature, message.encode()):
            last_block = self.last_block

            new_block = Block(index=last_block.index + 1,
                              transactions=self.unconfirmed_transactions,
                              timestamp=time.time(),
                              previous_hash=last_block.hash)

            proof = self.proof_of_work(new_block)
            self.add_block(new_block, proof)

            self.unconfirmed_transactions = []

            return True
        else:
            return False

    def get_chain(self):
        chain_data = []
        for block in self.chain:
            chain_data.append(block.__dict__)

            # return json.dumps(chain_data,indent=4)
        return chain_data


def create_chain_from_dump(chain_dump):  # 从给定的JSON数据创建区块链
    generated_blockchain = Blockchain()
    generated_blockchain.create_genesis_block()
    for idx, block_data in enumerate(chain_dump):
        if idx == 0:
            continue  # skip genesis block
        block = Block(block_data["index"],
                      block_data["transactions"],
                      block_data["timestamp"],
                      block_data["previous_hash"],
                      block_data["nonce"])
        proof = block_data['hash']
        added = generated_blockchain.add_block(block, proof)
        if not added:
            raise Exception("The chain dump is tampered!!")
    return generated_blockchain
